using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace Trigits
{
    class GameTable
    {
        private List<GridPoint> vertices;
        private List<GridFace> faces;
        private Texture2D texture;
        private float radius = 50;
        private float scale = 1;

        public GameTable(Texture2D texture, GraphicsDevice graphics, SpriteFont font, float scale)
        {
            this.scale = scale;

            this.texture = texture;
            vertices = new List<GridPoint>();
            faces = new List<GridFace>();

            Vector2 screenCenter = new Vector2(
                graphics.Viewport.Width / 2.0f,
                graphics.Viewport.Height / 2.0f
                ); // gebruik Viewport!

            for (int i = 0; i < 3; i++)
            {
                vertices.Add(
                    new GridPoint(
                        new Vector2(
                            screenCenter.X + radius * scale * (float)(Math.Cos(MathHelper.ToRadians(i * 120))),
                            screenCenter.Y + radius * scale * (float)(Math.Sin(MathHelper.ToRadians(i * 120)))
                                    )
                                )
                            );
            }

            faces.Add(new GridFace(vertices[0], vertices[1], vertices[2], font, scale));
        }

        /*
         * assumes clockwise triangles
         * adds empty faces adjacent to a filled face, where necessary
         * if the face is not filled, this function does nothing
         */
        private void addFaces(GridFace face, SpriteFont font)
        {
            if (face == null || !face.isFilled())
                return;

            GridPoint[] points = face.getPoints();
            
            addFaceToEdge(points[0], points[1], points[2], font);
            addFaceToEdge(points[1], points[2], points[0], font);
            addFaceToEdge(points[2], points[0], points[1], font);
        }

        internal bool addTriominoeToFace(GridFace face, Triominoe triominoe, SpriteFont font)
        {
            if (!triominoeMatchesFace(face, triominoe))
                return false;

            GridPoint[] points = face.getPoints();
            Vector2[] fv = face.getVectors();
            Vector2[] tv = triominoe.getVectors();
            int[] tvals = triominoe.getValues();

            int match = findMatchingVector(fv, tv);

            if (match < 0)
                return false;

            for (int i = 0; i < 3; i++)
            {
                if (points[i].getValue() == -1)
                    continue;

                if (points[i].getValue() == tvals[(i + match) % 3])
                    continue;
                
                return false;
            }
            
            for (int i = 0; i < 3; i++)
            {
                points[i].setValue(tvals[(i + match) % 3]);
            }

            face.setText();
            face.setFilled();
            addFaces(face, font);

            return true;
        }

        public void move(Vector2 distance)
        {
            foreach (GridPoint p in vertices)
            {
                p.move(distance);
            }

            foreach (GridPoint p in vertices)
            {
                p.resetMove();
            }

            foreach (GridFace f in faces)
            {
                f.move(distance);
            }
        }

        /**
         * Finds matching edges of two triangles. Triangles are represented by
         * three vectors: their vertices.
         * This method returns the number of the edge of the second triangle
         * that matches the first edge of the first triangle.
         * This means this method can return 0, 1 or 2.
         * If no edges match, this method returns -1.
         * If the Vector2 arrays don't represent triangles, i.e. don't
         * have Length of 3, this method also returns -1.
         */ 
        private int findMatchingVector(Vector2[] reference, Vector2[] search)
        {
            if (reference.Length != 3 || search.Length != 3)
                return -1;
            
            // get match by comparing the vertices
            for (int i = 0; i < 3; i++) // rotation of triominoe
            {
                Vector2[] d = new Vector2[3];
                for(int j = 0; j < 3; j++)
                {
                    d[j] = reference[j] - search[(i + j) % 3];
                }

                float a = d[0].Length() - d[(i + 1) % 3].Length();
                float b = d[0].Length() - d[(i + 2) % 3].Length();

                a *= a;
                b *= b;

                if (a < 5 && b < 5)
                    return i;
            }

            return -1;
        }

        // source: http://www.euclideanspace.com/maths/algebra/vectors/angleBetween/index.htm
        private float getAngle(Vector2 a, Vector2 b)
        {
            float dot = Vector2.Dot(a, b);
            dot = a.X * b.X + a.Y * b.Y;
            float ac = acos(dot);

            return MathHelper.ToDegrees(ac);
        }
        
        //acos(x) = atn(-x / sqrt(1-x^2)) + PI/2
        // source: http://answers.yahoo.com/question/index?qid=20080820041822AAzO5L1
        private float acos(float x)
        {
            double d = -x / Math.Sqrt(1 - x * x);
            d = Math.Atan(d) + Math.PI / 2;

            x = (float)d;
            return x;
        }
        private bool triominoeMatchesFace(GridFace face, Triominoe triominoe)
        {
            if (face.isPointingRight() != triominoe.isPointingRight())
                return false;
            
            return true;
        }

        /*
         * assumes Triangle(edgeStart, edgeEnd, opposite) is a clockwise triangle
         * Adds a face to an edge of another face
         */
        private void addFaceToEdge(GridPoint edgeStart, GridPoint edgeEnd, GridPoint opposite, SpriteFont font)
        {
            Vector2 p = new Vector2(
                (edgeEnd.getLocation().X + edgeStart.getLocation().X) / 2,
                (edgeEnd.getLocation().Y + edgeStart.getLocation().Y) / 2);

            float dx = p.X - opposite.getLocation().X;
            float dy = p.Y - opposite.getLocation().Y;

            p.X += dx;
            p.Y += dy;

            GridPoint point = getVertex(p.X, p.Y);
            if (point == null)
            {
                point = new GridPoint(p);
                vertices.Add(point);
            }

            if (this.getFace(edgeStart, edgeEnd, point) == null)
            {
                faces.Add(new GridFace(edgeStart, point, edgeEnd, font, scale));
            }
        }

        // gets a vertex that's 5 units or less away from the given coordinates
        public GridPoint getVertex(float x, float y)
        {
            foreach (GridPoint p in vertices)
            {
                if (p.getLocation().X - x < 5 && x - p.getLocation().X < 5 &&
                    p.getLocation().Y - y < 5 && y - p.getLocation().Y < 5)
                    return p;
            }
            
            return null;
        }

        public GridFace getFace(GridPoint a, GridPoint b, GridPoint c)
        {
            foreach (GridFace f in faces)
            {
                if (f.hasVertex(a) && f.hasVertex(b) && f.hasVertex(c))
                    return f;
            }

            return null;
        }

        public GridFace getFace(float x, float y)
        {
            foreach (GridFace face in faces)
            {
                if(face.containsPoint(x, y))
                    return face;
            }

            return null;
        }
        public List<GridFace> getFaces()
        {
            return faces;
        }

        public Vector2 getCenterFace()
        {
            Vector2[] v;
            Vector2 v2 = new Vector2();

            for (int i = faces.Count-1; i > -1; i--)
            {
                if ( faces[i].isFilled() )
                {
                    v = faces[i].getVectors();
                    v2 = faces[i].getCenter( v[0],v[1],v[2] );
                    break;
                }
              
            }
            
            return v2;
        }

        public void Draw(GraphicsDevice graphics, SpriteBatch spriteBatch, BasicEffect basicEffect, SpriteFont font)
        {
            spriteBatch.Begin();
            basicEffect.CurrentTechnique.Passes[0].Apply();

            foreach (GridFace f in faces)
            {
                f.Draw(graphics, spriteBatch, font);
            }

            spriteBatch.End();
        }

        public GridFace getFirstFace()
        {
            return faces[0];
        }
    }
}
